Redis 定位、架构与快速入门
从 Redis 的产品定位、部署形态、RESP 协议、命令原子性和 Go 客户端入门建立第一层技术基线。
第 1 章:Redis 定位、架构与快速入门
技术基线:截至 2026 年 6 月 21 日,Redis Open Source 当前公开下载版本为 8.8。本课程的 Go 客户端统一使用
github.com/redis/go-redis/v9,但本章示例只使用 Redis 6、7、8 均普遍支持的经典命令。(Redis)
1. 本章定位
本章解决四个基础问题:
- Redis 到底是缓存、数据结构服务器,还是数据库。
- Redis 与关系数据库、Memcached、本地缓存分别有什么边界。
- 单机、主从复制、Sentinel、Cluster 各自解决什么问题。
- Redis 客户端如何通过 RESP 协议操作数据库,以及“命令原子性”究竟意味着什么。
这几个问题看似基础,却决定了后续所有架构判断。如果一开始就把 Redis 简化为“一个很快的缓存”,就容易忽略它的数据结构、持久化和消息能力;如果把它当作“更快的 MySQL”,又会低估内存成本、查询能力、事务模型和一致性边界。
本章最终要建立的认识是:
Redis 是以内存数据结构和命令语义为核心的网络数据服务器。它可以扮演缓存、数据库、消息系统或协调组件,但能否承担某个角色,取决于数据模型、持久化、高可用、一致性和故障恢复要求,而不取决于产品名称。
Redis 官方仓库将 Redis 描述为缓存、数据结构服务器、文档及向量查询引擎,并列出了缓存、会话、NoSQL 数据存储、消息系统和实时分析等使用方式。(GitHub)
2. 学习目标
完成本章后,你应当能够:
- 用一句准确的话说明 Redis 的定位,而不是只说“Redis 是缓存”。
- 根据业务数据模型,判断应该使用 Redis、关系数据库、Memcached 还是本地缓存。
- 解释单机、主从、Sentinel、Cluster 的核心区别。
- 说明 Redis Server、Client、Database、Key、Value 之间的关系。
- 阅读一段简单的 RESP2 请求和响应。
- 准确解释 Redis 单命令原子性,以及它不保证什么。
- 使用
redis-cli执行PING、SET、GET、DEL、EXPIRE。 - 使用
go-redis/v9完成连接、超时控制、缓存未命中判断和基本错误处理。
2.1 本章边界与跳转
本章是 Redis 系列的入口地图,只给出定位、部署形态、协议和原子性的基础判断。后续遇到重复主题时,以对应专题章为主:Go 客户端实践见[第 2 章](/blog/tech/Redis/02.使用 Go 操作 Redis/),Key 与 TTL 见[第 3 章](/blog/tech/Redis/03.Key 设计、TTL 与生命周期管理/),高性能原理见[第 12 章](/blog/tech/Redis/12.Redis 高性能原理/),持久化见[第 14 章](/blog/tech/Redis/14.RDB、AOF 与数据恢复/),复制、Sentinel 与 Cluster 分别见第 15 章、[第 16 章](/blog/tech/Redis/16.Sentinel 高可用/)和[第 17 章](/blog/tech/Redis/17.Redis Cluster 分片与高可用/)。
3. 核心概念
3.1 Redis 是缓存、数据结构服务器,还是数据库
答案是:三者都可以,但这是三个不同维度的描述。
1. Redis 作为缓存
当权威数据保存在 MySQL、PostgreSQL、对象存储或其他系统中,Redis 只保存可重新构建的副本时,Redis 扮演的是缓存。
典型特征包括:
- 数据通常设置 TTL。
- 允许淘汰部分数据。
- Redis 数据丢失后可以从权威数据源恢复。
- 缓存失效主要影响性能,而不应直接造成业务事实永久丢失。
例如:
数据库:保存用户资料的权威版本
Redis:保存用户资料的热点副本
这里即使 Redis 整库丢失,也不应永久丢失用户资料,只会在缓存重建期间增加数据库压力。
2. Redis 作为数据结构服务器
Redis 不只是远程存取字符串。客户端可以直接发送:
- 原子计数命令。
- 集合交并差命令。
- 排行榜范围查询。
- 队列入队、出队命令。
- Stream 消费者组命令。
- 位图与概率统计命令。
这意味着应用不必总是执行“读取整个对象—本地修改—重新写回”。
例如,并发计数应优先使用:
INCR page:view:2026-06-21
而不是:
GET 当前值
在 Go 中加一
SET 新值
前者将读、修改、写合并成一个服务器命令;后者是三个独立步骤,存在并发覆盖问题。
3. Redis 作为数据库
如果 Redis 保存的是业务权威数据,而不是其他数据库的副本,那么 Redis 就承担了数据库角色。
这并非绝对错误,但必须同时回答:
- 数据能否完全放入可接受成本的内存中。
- 是否需要复杂关联、外键、唯一约束和临时查询。
- 是否可以接受 Redis 的持久化数据损失窗口。
- 是否可以接受异步复制和故障切换的一致性边界。
- 是否有独立备份,而不是把副本当作备份。
- 是否具备容量保护、监控和恢复演练。
因此,“Redis 是不是数据库”是产品能力问题;“Redis 能否作为这个业务的主数据库”则是架构选型问题。
3.2 Redis 与关系数据库、Memcached、本地缓存的区别
| 维度 | Redis | 关系数据库 | Memcached | 本地缓存 |
|---|---|---|---|---|
| 数据模型 | Key 加丰富数据类型 | 表、行、列、关系、索引 | 以简单缓存对象为主 | 由进程内数据结构决定 |
| 查询方式 | 主要按 Key、范围和特定结构操作 | SQL、索引、JOIN、聚合 | 主要按 Key 访问 | 直接内存访问 |
| 网络开销 | 有 | 有 | 有 | 通常没有 |
| 数据共享 | 多实例客户端共享 | 多客户端共享 | 多客户端共享 | 默认只在单个进程内共享 |
| 持久化 | 可配置 RDB、AOF 等 | 通常以持久化为核心 | 通常作为易失缓存 | 通常随进程退出丢失 |
| 事务能力 | 命令、事务、Lua;不等于关系事务 | 通常支持完整 ACID 事务 | 能力有限 | 取决于应用实现 |
| 数据结构语义 | 强 | 关系及约束语义强 | 较简单 | 完全由代码实现 |
| 一致性风险 | 多命令、复制、故障切换需要设计 | 依具体数据库与事务等级而定 | 主要作为缓存 | 多进程副本容易不一致 |
| 典型优势 | 低延迟、原子结构操作、实时状态 | 复杂查询、约束、事务、长期存储 | 简单对象缓存 | 极低访问延迟、无网络 RTT |
| 主要代价 | 内存成本、查询受限、运维复杂度 | 磁盘与事务开销、延迟通常更高 | 功能和数据结构较少 | 容量分散、失效传播困难 |
Memcached 更适合功能单一、对象模型简单的易失缓存;Redis 提供了更丰富的数据结构、持久化、复制和消息能力,但也因此具有更多配置和运行边界。Redis 的客户端侧缓存则试图结合本地缓存低延迟与服务端失效通知,不过仍需处理连接、失效和版本兼容问题。(Redis)
选型原则
- 需要复杂关系、约束和多行事务:优先关系数据库。
- 只需要简单对象缓存,功能越少越好:可比较 Memcached 和 Redis。
- 数据只在一个进程中使用且允许实例间短暂不一致:考虑本地缓存。
- 需要跨实例共享、TTL、计数、集合、排行榜、队列等能力:Redis 更合适。
- 同一业务常常会同时使用关系数据库、本地缓存和 Redis,而不是三选一。
3.3 Redis 适合与不适合的业务
适合的场景
- 热点数据缓存。
- Session、Token、验证码等短生命周期数据。
- 原子计数器、序列号和限流状态。
- 排行榜、标签、关注关系、去重集合。
- 短任务队列、事件流、消费者组。
- 实时特征、在线状态和临时业务状态。
- 在明确边界下实现锁、幂等键和协调状态。
不适合直接承担的场景
- 需要大量临时 SQL、复杂 JOIN 和外键约束。
- 大量冷数据长期保存,且绝大部分数据很少访问。
- 视频、安装包、原始图片等超大二进制对象。
- 必须依赖同步多数派提交的强一致核心账本。
- 跨大量记录的复杂事务。
- 无边界的历史分析和全量扫描。
- 团队无法承担持久化、复制、监控和故障恢复工作,却把 Redis 当作唯一数据源。
Redis 8 扩展了 JSON、搜索、时序、概率结构和向量能力,但“能够执行某种查询”不等于“在任何规模上都应该替代专用数据库”。
3.4 Redis 的四种基本部署形态
| 模式 | 主要目标 | 自动故障转移 | 数据分片 | 主要限制 |
|---|---|---|---|---|
| 单机 | 开发、测试、小规模或可降级业务 | 否 | 否 | 单点故障、容量受单机限制 |
| 主从复制 | 数据副本、读扩展、恢复基础 | 通常需要外部机制 | 否 | 默认异步复制,副本可能落后 |
| Sentinel | 非 Cluster 场景的监控、发现和自动切换 | 是 | 否 | 仍受单主节点容量限制 |
| Redis Cluster | 水平分片和节点级故障转移 | 是 | 是 | 跨 Slot 多 Key 操作受限,运维和客户端更复杂 |
单机
只有一个 Redis Server:
Application -> Redis
优点是简单;缺点是 Redis 进程、操作系统或宿主机故障都可能使服务不可用。
主从复制
一个主节点接收写入,一个或多个副本复制数据:
-> Replica 1
Application -> Primary
-> Replica 2
复制可用于数据副本和读扩展,但 Redis 默认采用异步复制。主节点成功响应客户端时,某次写入可能尚未到达即将被提升的副本。WAIT 可以降低部分风险,但不会把 Redis 变成强一致的 CP 系统。(Redis)
Sentinel
Sentinel 用于非 Cluster 部署,提供:
- 节点监控。
- 故障通知。
- 自动故障转移。
- 当前主节点的服务发现。
主节点失败时,Sentinel 可以选择一个副本提升为新主节点,并向客户端提供新的地址。Sentinel 自身也应部署多个实例,否则故障转移系统本身会成为单点。(Redis)
Redis Cluster
Cluster 将 Key 空间划分为 16384 个 Hash Slot,不同主节点负责不同 Slot。客户端计算 Key 对应的 Slot,然后访问负责该 Slot 的节点。
Cluster 同时提供:
- 水平分片。
- 集群拓扑。
- 重定向。
- 副本和故障转移。
但它并不是强一致数据库。Cluster 同样采用异步复制,且跨 Slot 的事务、Lua 和多 Key 命令受到限制。(Redis)
3.5 Server、Client、Database、Key、Value 模型
可以把 Redis 的基础模型理解为:
Client
|
| RESP 命令
v
Redis Server
|
+-- Database 0
| +-- key1 -> value1
| +-- key2 -> value2
|
+-- Database 1
+-- key1 -> another value
Redis Server
Redis Server 是实际保存数据和执行命令的进程。它负责:
- 接受客户端连接。
- 解析 RESP。
- 查找 Key。
- 执行命令。
- 管理过期、淘汰、持久化和复制。
- 返回响应。
Redis Client
Client 可以是:
redis-cli。- Go 程序中的
go-redis。 - 其他语言客户端。
- Redis Insight 等工具。
客户端通常维护长连接,而不是每执行一条命令就新建一次 TCP 连接。
Database
Redis Open Source 的非 Cluster 模式可以提供多个逻辑 Database,新连接默认选择 Database 0。
逻辑数据库只是命名空间:
- 不同数据库可以存在相同名称的 Key。
- 它们仍属于同一个 Redis 实例。
- 它们共享进程、内存、CPU、持久化文件和故障域。
- 不能把逻辑数据库视为强租户隔离。
Redis Cluster 只支持 Database 0,不能使用 SELECT 切换数据库。官方也不建议使用多个逻辑数据库承载彼此无关的应用。(Redis)
Key
Key 是字符串,用来唯一标识当前 Database 中的数据对象。
例如:
user:42:profile
order:20260621:1001
session:9f78d2
Value
Value 不是只有普通字符串。Redis 的 Value 可以是:
- String。
- Hash。
- List。
- Set。
- Sorted Set。
- Stream。
- Bitmap。
- HyperLogLog。
- JSON、Time Series 等扩展数据类型。
每个数据对象通过唯一 Key 访问,Key 和对应对象共同组成 Key-Value 对。(Redis)
TTL
TTL 属于 Key 的生命周期属性。Key 到期后,整个 Value 消失。
需要注意:
SET user:42 alice
EXPIRE user:42 300
是两个命令。第一个命令执行成功、第二个命令执行前客户端崩溃时,Key 可能没有 TTL。
更稳妥的写法是:
SET user:42 alice EX 300
3.6 RESP 协议的作用
RESP 是 Redis 客户端和服务器之间的序列化协议。
一般流程是:
- 客户端把命令编码成 RESP。
- 服务器解析命令。
- 服务器执行命令。
- 服务器把结果编码成 RESP。
- 客户端将 RESP 转换成 Go 的字符串、整数、切片或错误。
Redis 通常将客户端命令编码为 Bulk String 数组,第一个元素是命令名,后续元素是参数;服务器则返回对应的 RESP 类型。RESP 是二进制安全协议,但使用 ASCII 控制字符和 \r\n 作为结构分隔。(Redis)
例如:
SET demo:name Redis
在 RESP2 中可以编码为:
*3\r\n
$3\r\n
SET\r\n
$9\r\n
demo:name\r\n
$5\r\n
Redis\r\n
服务器成功响应:
+OK\r\n
查询一个不存在的 Key 时,RESP2 的 Null Bulk String 是:
$-1\r\n
go-redis 会把部分命令的这种“结果不存在”映射为 redis.Nil,它不是网络异常,也不是 Redis Server 故障。(Go Packages)
RESP 为什么是面试重点
理解 RESP 后,可以解释:
- 为什么 Redis 支持多语言客户端。
- 为什么 Pipeline 能减少网络往返。
- 为什么错误、空值、整数和数组会映射成不同客户端类型。
- 为什么客户端仍需要进行序列化和反序列化。
- 为什么协议简单不等于没有网络成本。
3.7 Redis 命令原子性应该如何理解
正确理解
对于同一个 Redis 节点,单条命令在逻辑执行过程中不会被另一条命令插入一半。
例如:
INCR counter
读取当前值、加一、写回,是一个完整命令。并发客户端不会同时读到旧值后相互覆盖。
原子性不等于以下概念
| 概念 | 单命令原子性是否保证 |
|---|---|
| 命令执行期间不被其他命令插入 | 是 |
| 数据已经写入磁盘 | 否 |
| 数据已经复制到副本 | 否 |
| 故障切换后一定保留 | 否 |
| 多条独立命令整体原子 | 否 |
| 失败后自动回滚 | 否 |
| 跨 Cluster 节点强一致 | 否 |
| 整个业务流程只执行一次 | 否 |
下面的代码不是原子操作:
GET stock
stock = stock - 1
SET stock
两个客户端可能都读取到 stock=1,随后都写回 0,导致超卖。
可选方案包括:
- 使用服务器已有的原子命令。
MULTI/EXEC。WATCH乐观锁。- Lua 脚本或 Redis Functions。
- 将状态更新放回具有事务约束的数据库。
Redis 事务能够让一组已入队命令连续执行,但它不提供关系数据库式的自动回滚。Pipeline 只减少网络往返,本身也不提供事务原子性。(Redis)
4. 命令与 Go 使用方法
4.1 使用 Docker 启动本地 Redis
本地学习可以运行:
docker run -d \
--name redis-8-8 \
-p 127.0.0.1:6379:6379 \
redis:8.8
进入容器中的 redis-cli:
docker exec -it redis-8-8 redis-cli
也可以使用本机安装的 CLI:
redis-cli -h 127.0.0.1 -p 6379
Redis 官方 Docker 文档使用 redis:<version> 镜像启动 Server,并可通过容器内或本地的 redis-cli 连接。生产环境不能照搬一个无 ACL、无 TLS、无持久化规划的本地开发命令。(Redis)
4.2 基础 redis-cli 命令
检查连通性
127.0.0.1:6379> PING
PONG
PING 成功只能说明当前连接能够得到 Redis 响应,不代表复制、持久化、内存或业务数据都正常。
写入 Key
127.0.0.1:6379> SET interview:greeting "hello redis"
OK
设置过期时间
127.0.0.1:6379> EXPIRE interview:greeting 60
(integer) 1
返回值:
1:成功设置过期时间。0:Key 不存在或条件未满足。
查看 TTL
127.0.0.1:6379> TTL interview:greeting
(integer) 57
常见特殊结果:
-1:Key 存在,但没有过期时间。-2:Key 不存在。
读取 Value
127.0.0.1:6379> GET interview:greeting
"hello redis"
删除 Key
127.0.0.1:6379> DEL interview:greeting
(integer) 1
再次读取
127.0.0.1:6379> GET interview:greeting
(nil)
Key、Value、过期时间以及 SCAN、KEYS 的使用边界均属于 Redis Keyspace 模型。官方明确警告,不应在大规模生产 Keyspace 中把 KEYS 当作常规应用查询。(Redis)
4.3 使用 go-redis/v9
安装依赖:
go get github.com/redis/go-redis/v9
最小示例:
package main
import (
"context"
"errors"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Username: "",
Password: "",
DB: 0,
DialTimeout: 2 * time.Second,
ReadTimeout: 1 * time.Second,
WriteTimeout: 1 * time.Second,
PoolTimeout: 2 * time.Second,
})
defer func() {
if err := rdb.Close(); err != nil {
log.Printf("close redis client: %v", err)
}
}()
if err := run(context.Background(), rdb); err != nil {
log.Fatal(err)
}
}
func run(parent context.Context, rdb *redis.Client) error {
// 这个 deadline 是这一组操作的总时间预算。
ctx, cancel := context.WithTimeout(parent, 3*time.Second)
defer cancel()
pong, err := rdb.Ping(ctx).Result()
if err != nil {
return fmt.Errorf("ping redis: %w", err)
}
fmt.Println("PING:", pong)
const key = "interview:user:42"
// expiration=0 表示这一步暂时不设置 TTL。
if err := rdb.Set(ctx, key, "alice", 0).Err(); err != nil {
return fmt.Errorf("set %q: %w", key, err)
}
expireSet, err := rdb.Expire(ctx, key, 10*time.Minute).Result()
if err != nil {
return fmt.Errorf("expire %q: %w", key, err)
}
if !expireSet {
return fmt.Errorf("expire %q: key does not exist", key)
}
value, err := rdb.Get(ctx, key).Result()
switch {
case err == nil:
fmt.Println("GET:", value)
case errors.Is(err, redis.Nil):
return fmt.Errorf("get %q: unexpected cache miss", key)
default:
return fmt.Errorf("get %q: %w", key, err)
}
deleted, err := rdb.Del(ctx, key).Result()
if err != nil {
return fmt.Errorf("del %q: %w", key, err)
}
fmt.Println("DEL count:", deleted)
_, err = rdb.Get(ctx, key).Result()
switch {
case errors.Is(err, redis.Nil):
fmt.Println("GET after DEL: cache miss")
case err != nil:
return fmt.Errorf("get after del %q: %w", key, err)
default:
return fmt.Errorf("get after del %q: key still exists", key)
}
return nil
}
官方 Go 指南使用 redis.NewClient 创建客户端,并通过带 context.Context 的 Set、Get 等方法执行命令。go-redis 的 Client 内部管理连接池,可以被多个 goroutine 并发共享,不应为每个请求创建一个新 Client。(Redis)
关键代码说明
1. Context 与网络超时不是同一个维度
ctx, cancel := context.WithTimeout(parent, 3*time.Second)
Context 控制本次调用链的时间预算。
DialTimeout
ReadTimeout
WriteTimeout
PoolTimeout
这些参数分别限制建连、套接字读写以及等待连接池资源的时间。
生产代码一般应同时具备:
- 上游请求 deadline。
- Redis 命令 deadline。
- 客户端网络超时。
- 连接池等待上限。
2. redis.Nil 不是真正异常
errors.Is(err, redis.Nil)
它通常表示 GET 类命令没有找到值,应当进入缓存未命中流程,而不是直接返回 500。
但并非所有命令在数据不存在时都会返回 redis.Nil。例如,一些集合类命令可能返回空集合和 nil error。因此错误处理必须依据具体命令语义。
3. SET 和 EXPIRE 是两个命令
本例为了演示 EXPIRE,故意分开执行。
生产环境如果要求 Key 从创建开始就必须带 TTL,应写成:
if err := rdb.Set(ctx, key, "alice", 10*time.Minute).Err(); err != nil {
return fmt.Errorf("set with ttl %q: %w", key, err)
}
这会生成一个带过期参数的 SET 命令,避免两个命令之间的故障窗口。
4. 并发安全边界
可共享:
*redis.Client
不应默认在多个 goroutine 之间共享:
- 某个正在组装命令的 Pipeline。
- 某个 WATCH 事务对象。
- 某条具有连接状态的专用连接。
- 已经取消的 Context。
Client 并发安全,不等于所有从 Client 创建出来的临时对象都并发安全。
5. 典型业务场景
| 场景 | 适用方式 | 不适用情况 | 数据量要求 | 一致性要求 | 性能风险 | 可替代方案 |
|---|---|---|---|---|---|---|
| 热点对象缓存 | String、Hash、JSON,配合 TTL | 数据无法从其他来源恢复 | 热数据集应适配内存预算 | 通常允许短暂旧值 | 热 Key、缓存击穿、大 Value | 本地缓存、CDN、数据库缓存 |
| Session 与 Token | 带 TTL 的 String 或 Hash | 必须在跨地域故障中零丢失 | 通常中等,生命周期明确 | 需明确切换时会话丢失边界 | TTL 集中过期、热点用户 | 数据库、签名自包含 Token |
| 计数器与限流 | INCR、Lua、Sorted Set 等 | 要求跨区域全局强一致计数 | 状态通常较小 | 允许明确误差或失败策略 | 热 Key、重试重复计数 | 数据库行锁、专用限流网关 |
| 排行榜 | Sorted Set | 需要复杂多维分析 | 单榜规模和更新频率可控 | 通常允许短暂复制延迟 | 大范围查询、单榜热点 | 搜索引擎、OLAP 系统 |
| 消息与事件 | Streams、List、Pub/Sub | 需要复杂路由、长期保留、跨地域强保证 | 必须控制积压和裁剪 | 需要设计重复、丢失和 ACK | 慢消费者、积压占满内存 | Kafka、Pulsar、RabbitMQ |
| 实时业务状态 | Key、Hash、Set 等 | 需要复杂关系与跨记录事务 | 工作集必须可控 | 由持久化与复制方案决定 | 内存膨胀、故障切换丢写 | 关系数据库、分布式 KV |
| 简单对象缓存 | String 序列化值 | 只需最简单缓存且团队希望极简运维 | 适配内存即可 | 数据应可重建 | 序列化、大对象、网络 RTT | Memcached、本地缓存 |
选型时不能只比较平均延迟,还要比较最坏情况下的数据丢失、重建成本、流量回源和运维复杂度。
6. 底层实现
6.1 核心数据结构
从逻辑上看,一个 Redis Database 可以理解为一个大的 Keyspace 字典:
Key -> Value Object
Key 通常由 Redis 的动态字符串结构保存;Value 对象记录:
- 逻辑类型。
- 内部编码。
- 指向具体数据的引用。
- 与内存管理相关的元信息。
过期时间还需要额外的数据结构记录 Key 与到期时间之间的关系。
之所以选择哈希字典作为 Keyspace 的核心结构,是因为绝大多数 Redis 请求首先都要完成:
根据 Key 找到 Value
平均 O(1) 的 Key 查找适合低延迟访问,而代价是:
- 每个 Key 都有额外元数据。
- 哈希表需要预留桶空间。
- 扩容和渐进式 rehash 会消耗 CPU。
- 无法自然提供关系数据库式的临时查询。
6.2 核心命令复杂度
| 命令 | 命令参考复杂度 | 端到端需要额外考虑 |
|---|---|---|
PING | O(1) | 网络 RTT、连接池等待 |
GET | O(1) 查找 | 返回 B 字节至少需要传输和复制 B 字节 |
SET | 通常标记为 O(1) | 大 Value 的接收、分配和复制成本 |
EXPIRE | O(1) | 后续过期删除仍需消耗 CPU |
DEL | 与 Key 数量及 Value 释放有关 | 大复杂对象同步释放可能产生延迟 |
SELECT | O(1) | Cluster 中不支持 |
因此,面试中不能只说:
GET 是 O(1),所以无论 Value 多大都一样快。
更准确的说法是:
哈希定位通常是平均 O(1),但返回结果的网络传输、内存复制和客户端反序列化至少与 Value 大小相关。
Redis 命令参考将 SET 标记为 O(1),但这类复杂度主要描述服务器内部的数据结构操作,并不消除数据长度带来的网络和内存成本。(Redis)
6.3 空间成本
一个业务对象占用的 Redis 内存通常不仅是业务数据长度,还包括:
Key 字节
+ Value 字节
+ Redis 对象元数据
+ 哈希表桶和指针
+ 过期时间记录
+ 内部数据结构开销
+ 内存分配器开销
+ 内存碎片
因此,数据库中 10 GB 的序列化数据迁移到 Redis 后,实际内存占用不一定是 10 GB。
容量规划至少要考虑:
- 平均 Key 长度。
- 平均 Value 长度。
- Key 数量。
- 数据类型。
- TTL 比例。
- 峰值增长。
- 复制节点数量。
- fork 和 Copy-on-Write 的额外内存风险。
- 内存碎片及操作系统 RSS。
6.4 为什么命令执行通常采用串行化模型
Redis 的大部分核心命令执行,从观察者角度看主要由一个执行线程串行处理。这带来了:
- 减少共享数据结构上的锁竞争。
- 单命令更容易提供原子语义。
- 执行路径短。
- 行为相对容易推理。
但代价也很明确:
- 一个长时间命令可能阻塞其他客户端。
- 大 Key 删除、大范围集合操作会放大尾延迟。
- 单实例命令吞吐最终受到单执行核心约束。
现代 Redis 并非“整个进程只有一个线程”。网络 I/O、持久化、异步释放和其他后台任务可能使用额外线程;准确说法应是“核心命令执行视角主要是单线程串行化”。Redis 官方性能文档也明确区分了命令执行线程和现代版本使用的其他线程。(Redis)
6.5 版本差异
本章需要记住三点:
- Redis 8.8 是当前技术基线,但
PING、SET、GET、DEL、EXPIRE等示例并非 8.8 专属。 - Redis 支持 RESP2 与 RESP3;
go-redis可以配置所用协议版本。(GitHub) - 非 Cluster Redis 可以使用逻辑数据库;Redis Cluster 只支持 Database 0。(Redis)
后续涉及新命令时,必须标注其引入版本,不能把 Redis 8 的能力写成 Redis 6 已支持。
7. 高性能、高并发、高可用分析
7.1 高性能
Redis 快并不是单一原因造成的。
1. 内存是主要工作介质
大多数命令直接访问内存中的数据结构,避免了普通磁盘随机访问路径。
但“使用内存”并不足以解释全部性能,因为:
- 本地缓存也使用内存,却没有网络。
- 低效数据结构即使在内存中也可能很慢。
- 大量内存分配、复制和碎片仍会消耗 CPU。
- 持久化、复制仍可能涉及磁盘和网络。
2. 数据结构和命令语义匹配
Redis 不是把所有东西都当作不透明 Blob。
例如:
- 计数直接使用
INCR。 - 排名直接使用 Sorted Set。
- 集合关系直接使用 Set。
- 队列直接使用 List 或 Streams。
服务器可以在数据所在地执行操作,避免大量数据在客户端和服务器之间来回传输。
3. 命令执行路径相对短
串行化执行减少了核心数据结构上的锁竞争,但要求命令本身足够短。
4. RESP 和长连接
客户端通常复用连接;RESP 的编码和解析相对直接。
5. Pipeline
普通顺序调用:
请求1 -> 响应1 -> 请求2 -> 响应2
Pipeline:
请求1、请求2、请求3 -> 响应1、响应2、响应3
它主要减少网络 RTT,不会自动使命令成为事务。(Redis)
常见瓶颈
- CPU:复杂命令、Lua、大集合运算。
- 内存:碎片、频繁分配、容量不足。
- 网络:大 Value、高 QPS、跨机房调用。
- 磁盘:AOF fsync、RDB、fork 与 COW。
- 客户端:连接池等待、序列化、重试风暴。
7.2 高并发
Redis 可以同时维护大量客户端连接,但“连接并发”不等于“所有命令并行修改数据”。
竞争
同一个 Key 上的大量请求会形成 HotKey:
10000 个请求 -> product:1001
即使命令是 O(1),也会集中消耗:
- 单节点 CPU。
- 单网卡带宽。
- 单 Key 所在 Cluster 节点的能力。
- 客户端连接池。
原子性
单命令原子不意味着业务流程原子。
检查库存
创建订单
扣减库存
写消息
不可能因为其中某一步用了 Redis 就自动成为一个事务。
重试
客户端超时不代表命令一定没有执行。
可能发生:
客户端发送 INCR
Redis 已执行
响应在网络中丢失
客户端超时并重试
计数增加两次
因此,重试写命令时必须判断:
- 命令是否幂等。
- 是否需要幂等 Key。
- 是否允许至少一次执行。
- 是否需要读取最终状态确认。
惊群与流量突增
热门缓存同时过期时,大量请求可能同时回源数据库。Redis 本身仍然很快,但后端数据库会被打垮。
高并发设计必须同时考虑:
- TTL 抖动。
- 请求合并。
- 热点拆分。
- 降级。
- 回源限流。
7.3 高可用
单机
单机没有节点级高可用,故障后需要:
- 重启。
- 恢复持久化文件。
- 修改连接地址或等待原地址恢复。
主从复制
主从提供数据副本,但默认是异步复制:
Primary 已响应客户端
Replica 尚未收到写入
Primary 故障
落后的 Replica 被提升
该写入可能丢失。
Sentinel
Sentinel 能自动发现故障并提升副本,但不能:
- 消除异步复制的数据丢失窗口。
- 进行数据分片。
- 保证零 RTO。
- 替代备份。
Cluster
Cluster 同时处理分片和故障切换,但客户端必须理解:
- Slot。
- MOVED、ASK 重定向。
- 节点拓扑变化。
- 多 Key 同 Slot 限制。
- 故障切换时可能出现的短暂错误和数据丢失。
Redis 复制文档明确指出,即使使用 WAIT,一组 Redis 节点也不会因此变为强一致 CP 系统;Sentinel 和 Cluster 的高可用能力仍建立在异步复制边界之上。(Redis)
必须区分四个概念
| 概念 | 解决的问题 |
|---|---|
| 缓存 | 减少后端访问和延迟 |
| 持久化 | 进程重启后恢复数据 |
| 高可用 | 节点故障后尽快恢复服务 |
| 备份 | 应对误删除、逻辑损坏和灾难恢复 |
有副本不等于有备份;开启 AOF 不等于自动高可用;使用 Sentinel 不等于不会丢数据。
8. 常见错误与生产事故
8.1 把未保护的 Redis 暴露到公网
- 现象:数据被删除、写入陌生 Key、服务器资源异常,甚至 Redis 配置被恶意修改。
- 根因:直接暴露端口,未配置网络隔离、ACL、认证或 TLS。
- 排查方法:检查安全组、监听地址、ACL、客户端列表、审计日志和异常命令。
- 修复方案:立即隔离实例、轮换凭证、检查数据和宿主机完整性,从可信备份恢复。
- 如何预防:默认内网访问、最小权限 ACL、TLS、网络白名单,禁止把本地 Docker 命令直接照搬到公网。
8.2 把 Redis 当缓存,却不设置 TTL 和容量上限
- 现象:内存持续上涨,最终写入报错、触发淘汰或进程被操作系统终止。
- 根因:业务不断创建 Key,没有生命周期管理,也没有容量保护。
- 排查方法:检查
INFO MEMORY、Key 数量、TTL 分布、大 Key 和内存增长速率。 - 修复方案:补充 TTL、删除无用数据、配置合理
maxmemory和淘汰策略。 - 如何预防:所有缓存 Key 明确所有者、TTL、最大数量和降级策略。
8.3 误以为多条命令自动原子
- 现象:并发计数丢失、库存被覆盖、状态机出现非法跳转。
- 根因:采用
GET—本地计算—SET,却把每条命令的原子性误认为整个流程原子。 - 排查方法:复现并发执行,检查客户端请求顺序和最终数据。
- 修复方案:使用原子命令、Lua、WATCH 或把事务放到权威数据库。
- 如何预防:评审时明确标注“原子边界究竟覆盖哪几条操作”。
8.4 SET 成功但 EXPIRE 没有执行
- 现象:少量本应过期的 Session 或验证码长期存在。
- 根因:
SET和EXPIRE分成两个命令,客户端在中间超时、崩溃或断连。 - 排查方法:查找对应前缀下 TTL 为
-1的异常 Key。 - 修复方案:使用带
EX、PX的SET,或用 Lua 保证组合操作。 - 如何预防:创建时必须过期的 Key 禁止使用裸
SET。
8.5 用逻辑 Database 做强租户隔离
- 现象:误执行
FLUSHALL影响所有业务;迁移到 Cluster 时发现只能使用 DB 0。 - 根因:把逻辑 Database 误认为独立实例或安全边界。
- 排查方法:检查不同业务是否共享同一实例、凭证、资源和持久化文件。
- 修复方案:使用独立实例或 Cluster,并通过 Key 前缀和 ACL 做进一步隔离。
- 如何预防:无关业务不共用同一 Redis 实例;逻辑 DB 只用于有限命名空间需求。
8.6 大 Value 或全量命令阻塞实例
- 现象:Redis CPU 不高但 P99 延迟突然升高,所有客户端短暂超时。
- 根因:执行
KEYS *、返回超大 Value、删除大型集合或进行大范围集合运算。 - 排查方法:查看
SLOWLOG、命令统计、大 Key、网络流量和延迟事件。 - 修复方案:改用
SCAN、拆分大 Key、限制返回范围,必要时使用异步释放。 - 如何预防:上线前评估命令复杂度和最坏数据规模,不只看平均规模。
官方 Keyspace 文档明确警告,大型数据库中的 KEYS 会阻塞服务器,不应出现在常规应用代码中。(Redis)
8.7 每个 HTTP 请求都创建一个 Redis Client
- 现象:连接数暴涨、端口耗尽、握手延迟增加,Redis 出现大量短连接。
- 根因:误以为 Client 不能并发共享。
- 排查方法:检查
CLIENT LIST、连接创建速率、应用 goroutine 和 socket 数量。 - 修复方案:在进程级共享一个或少量
redis.Client,交由内部连接池复用。 - 如何预防:Client 在应用启动时创建,在进程退出时关闭;每个请求只创建 Context。
9. 方案选型与权衡
9.1 部署方式选择
| 方案 | 适合场景 | 分片 | 自动切换 | 数据丢失风险 | 运维复杂度 |
|---|---|---|---|---|---|
| 单机无持久化 | 可完全重建的开发或临时缓存 | 否 | 否 | 高 | 低 |
| 单机加持久化 | 小规模、可接受恢复时间 | 否 | 否 | 取决于持久化策略 | 低 |
| 主从复制 | 读扩展、人工切换、有副本需求 | 否 | 通常否 | 存在异步复制窗口 | 中 |
| Sentinel | 数据可放入单主节点,要求自动切换 | 否 | 是 | 存在异步复制窗口 | 中 |
| Cluster | 单节点容量不足或需要水平扩展 | 是 | 是 | 存在异步复制窗口 | 高 |
| 托管 Redis | 团队希望降低日常运维负担 | 依产品 | 通常有 | 依产品和配置 | 平台侧低、成本较高 |
9.2 选型顺序
不要先问“要不要上 Cluster”,应先回答:
- 峰值数据量和未来增长是否能放入单节点。
- 单节点 CPU 和网络吞吐是否足够。
- 可接受的 RTO 和 RPO 是多少。
- 数据是缓存还是权威数据。
- 是否存在跨 Key 事务和多 Key 操作。
- 是否能接受 Cluster 的同 Slot 限制。
- 客户端是否支持 Sentinel 或 Cluster。
- 团队能否维护相应复杂度。
示例判断
- 20 GB 可重建缓存,允许几分钟中断:单机或简单主从可能已经足够。
- 100 GB 会话数据,要求自动切换但无需分片:可考虑 Sentinel。
- 2 TB 热数据,单节点无法承载:需要 Cluster 或其他分布式方案。
- 核心账务,要求同步多数派提交和复杂事务:Redis 通常不应成为唯一账本。
不存在脱离业务约束的“最佳 Redis 架构”。
10. 高频面试题
10.1 Redis 到底是缓存还是数据库
问题
Redis 是缓存、数据库还是数据结构服务器?
推荐回答
面试现场版:
Redis 是以内存数据结构和命令语义为核心的数据服务器。它可以作为缓存,也可以作为数据库、消息系统或协调组件。具体角色取决于数据是否权威、是否可重建,以及持久化、高可用和一致性设计。
展开:
当数据只是其他数据库的副本时,它是缓存;当应用通过集合、排行榜、计数器等服务器端结构操作数据时,它是数据结构服务器;当 Redis 保存业务权威状态时,它承担数据库角色。但承担数据库角色并不意味着具有关系数据库相同的查询、约束和事务能力。
面试官追问
- Redis 能否作为订单数据库?
- 缓存和数据库最本质的区别是什么?
- Redis 数据丢失后如何恢复?
常见错误回答
“Redis 就是缓存,不能存正式数据。”或者“Redis 有持久化,所以可以无条件替代 MySQL。”
评分点
- 初级:知道 Redis 是内存 Key-Value 系统。
- 中级:能区分缓存角色和权威数据。
- 高级:能结合持久化、复制、RPO、查询模型和内存成本回答。
10.2 Redis 为什么不能简单替代关系数据库
问题
Redis 很快,为什么不把所有数据都放到 Redis?
推荐回答
面试现场版:
因为性能不是唯一目标。关系数据库擅长复杂查询、约束、事务和长期持久化;Redis 擅长低延迟 Key 访问和数据结构操作。把全部数据放进 Redis 会增加内存成本,也无法自然获得 JOIN、外键和完整事务语义。
展开:
选型必须同时考虑:
- 数据访问模型。
- 一致性和事务。
- 容量及成本。
- 历史数据保存。
- 临时查询。
- 故障恢复。
热点状态使用 Redis,长期权威数据使用关系数据库,是很常见的组合。
面试官追问
- 哪类数据适合只放 Redis?
- Redis 8 有 Search 后是否可以替代 Elasticsearch?
- 关系数据库也有缓存,为什么还需要 Redis?
常见错误回答
只回答“Redis 数据在内存里,太贵”,没有提数据模型和事务。
评分点
- 初级:知道 Redis 内存成本高。
- 中级:能比较查询和事务。
- 高级:能讨论系统边界和组合架构。
10.3 Redis、Memcached 和本地缓存如何选择
问题
Redis、Memcached、本地缓存分别适合什么场景?
推荐回答
面试现场版:
本地缓存没有网络 RTT,但各应用实例之间默认不一致;Memcached 更专注简单易失对象缓存;Redis 提供共享缓存、丰富数据结构、TTL、持久化、复制和消息能力。应根据共享范围、失效模型和操作语义选择。
展开:
- 单进程热点数据:本地缓存。
- 简单共享对象缓存:比较 Redis 与 Memcached。
- 需要排行榜、计数、集合、队列:Redis。
- 高性能系统可以使用本地缓存加 Redis 的两级缓存。
两级缓存会引入失效传播和短暂不一致,不能只看到性能收益。
面试官追问
- 两级缓存如何失效?
- 本地缓存容量如何控制?
- Redis 故障后本地缓存能否继续服务?
常见错误回答
认为本地缓存一定优于 Redis,忽略多实例一致性和容量分散。
评分点
- 初级:能说出是否需要网络。
- 中级:能比较数据共享和功能。
- 高级:能讨论失效通知、版本号和故障降级。
10.4 Redis 为什么快
问题
Redis 高性能的原因是什么?
推荐回答
面试现场版:
内存访问只是原因之一。Redis 还使用高效数据结构、相对短的命令路径、事件驱动网络模型、主要串行化的命令执行以减少锁竞争,并支持长连接、批量命令和 Pipeline。
展开:
还应强调:
- O(1) 不代表大 Value 没有成本。
- 单线程执行会受到慢命令影响。
- 网络、CPU、内存和持久化磁盘都可能成为瓶颈。
- Redis 快不意味着每种用法都快。
面试官追问
- Redis 的瓶颈通常是 CPU 还是网络?
- Pipeline 为什么能提升吞吐?
- 大 Key 为什么会拖慢其他请求?
常见错误回答
“因为 Redis 是单线程,而且数据在内存。”
单线程不是天然更快,内存也不是完整答案。
评分点
- 初级:回答内存。
- 中级:补充事件循环、数据结构和锁竞争。
- 高级:能分析网络 RTT、尾延迟和慢命令。
10.5 Redis 是单线程的吗
问题
Redis 是不是单线程的?
推荐回答
面试现场版:
不能简单说 Redis 完全是单线程。核心命令执行视角主要是单线程串行化,但网络 I/O、持久化、异步释放和后台任务可以使用其他线程或进程。
展开:
这种设计减少核心数据结构锁竞争,也使单命令原子性更容易实现;但长时间命令会阻塞后续命令,单实例的命令执行能力也受单核心限制。
面试官追问
- I/O Threads 是否让命令并行执行?
- 为什么一个慢命令会影响其他客户端?
- 多核机器如何扩展 Redis?
常见错误回答
“Redis 从头到尾只有一个线程。”
评分点
- 初级:知道主线程执行命令。
- 中级:能区分网络和后台任务。
- 高级:能分析慢命令、分片和多实例扩展。
10.6 Redis 命令原子性意味着什么
问题
Redis 命令是原子的吗?
推荐回答
面试现场版:
单条命令在同一节点的逻辑执行过程中不会被其他命令插入,但它不保证持久化、复制、故障切换不丢失,也不保证多条独立命令整体原子。
展开:
INCR 是原子计数;GET 后在客户端加一再 SET 则不是。多命令原子操作要考虑事务、WATCH、Lua 或已有组合命令。
面试官追问
- 原子性与持久性有什么区别?
- Pipeline 是否原子?
- Lua 为什么可以实现组合原子操作?
常见错误回答
“Redis 是单线程,所以所有业务操作都是原子的。”
评分点
- 初级:知道单命令原子。
- 中级:能指出多命令竞态。
- 高级:能区分原子性、隔离性、持久性和幂等性。
10.7 单机、主从、Sentinel、Cluster 有什么区别
问题
这四种部署模式分别解决什么问题?
推荐回答
面试现场版:
单机最简单但有单点;主从提供数据副本和读扩展,但通常没有自动故障转移;Sentinel 在非分片场景提供监控、服务发现和自动切换;Cluster 同时提供数据分片和节点故障转移。
展开:
还要说明:
- 主从和两种高可用模式都存在异步复制边界。
- Sentinel 不解决单节点容量问题。
- Cluster 会限制跨 Slot 多 Key 操作。
- 自动切换不等于零数据丢失。
面试官追问
- Sentinel 为什么需要三个实例?
- Cluster 为什么有 16384 个 Slot?
- 主从复制能否当作备份?
常见错误回答
“主从就是高可用,Cluster 就是强一致。”
评分点
- 初级:能说出主从和 Cluster。
- 中级:理解 Sentinel 和自动切换。
- 高级:能讨论异步复制、客户端发现和分片约束。
10.8 Redis 能否作为主数据库
问题
什么情况下可以把 Redis 作为主数据库?
推荐回答
面试现场版:
当数据模型以 Key 和 Redis 数据结构操作为主,数据集适合内存,业务接受 Redis 的持久化与复制边界,并具备备份、恢复和容量治理时,可以考虑。复杂关系、严格多记录事务和强一致核心账本通常不适合。
展开:
必须明确:
- RPO。
- RTO。
- 持久化策略。
- 副本数量。
- 备份保留。
- 故障切换丢写风险。
- 数据迁移和容量增长方案。
面试官追问
- 只开启 AOF everysec 会丢多少数据?
- Sentinel 能否保证已成功写入不丢失?
- 备份和副本有什么区别?
常见错误回答
“开了 AOF 就不会丢数据。”
评分点
- 初级:知道要持久化。
- 中级:知道要复制和备份。
- 高级:能根据业务给出明确 RPO/RTO 和失败模式。
10.9 Redis 的逻辑 Database 有什么问题
问题
可以用 DB 0、DB 1 分别存放两个业务吗?
推荐回答
面试现场版:
技术上可以,但不建议用逻辑 Database 隔离无关业务。它们仍共享同一进程、资源、持久化和故障域,而且 Redis Cluster 只支持 DB 0。
展开:
更稳妥的方式是:
- 独立 Redis 实例或独立集群。
- 统一 Key 前缀。
- ACL 最小权限。
- 独立容量和监控。
面试官追问
- 不同 DB 能否使用相同 Key?
FLUSHALL会影响哪些 DB?- 为什么 Cluster 不支持 SELECT?
常见错误回答
把逻辑 DB 当作 MySQL 中完全独立的 Schema 或租户。
评分点
- 初级:知道 SELECT。
- 中级:知道资源仍然共享。
- 高级:能联系 Cluster、权限和故障隔离。
10.10 RESP 有什么作用
问题
RESP 是什么,为什么需要了解?
推荐回答
面试现场版:
RESP 是 Redis 客户端和服务器之间的序列化协议。客户端把命令编码为数组和字符串,服务器返回字符串、整数、数组、空值或错误等类型。理解 RESP 有助于分析客户端映射、Pipeline 和协议错误。
展开:
RESP2 和 RESP3 的类型表达不同,但应用一般通过客户端库使用。go-redis 会把 RESP 中的空结果映射成相应的 Go 结果,例如 GET 未命中时的 redis.Nil。
面试官追问
- RESP 是否只能传文本?
- Redis 如何传二进制 Value?
- Pipeline 是否改变 RESP 格式?
常见错误回答
“RESP 就是 Redis 的 JSON 协议。”
评分点
- 初级:知道是通信协议。
- 中级:能解释请求数组和响应类型。
- 高级:能联系二进制安全、Pipeline 和错误处理。
10.11 Redis 故障时应用应该失败开放还是失败关闭
问题
Redis 突然不可用,应用应该继续服务还是直接拒绝请求?
推荐回答
面试现场版:
取决于 Redis 扮演的角色。普通缓存通常可以失败开放,回源数据库,但必须限流和防止雪崩;权限、风控和限流状态可能需要失败关闭;Session 和幂等状态则要根据安全与可用性目标选择。
展开:
示例:
- 商品详情缓存:可以回源,但要限制回源并发。
- 风控黑名单:通常不能因为 Redis 故障而默认放行。
- 登录 Session:可能要求重新登录,也可能使用短期本地容灾。
- 幂等键:Redis 故障时继续处理可能导致重复订单。
面试官追问
- 缓存回源如何防止数据库被打垮?
- 限流系统故障时应该放行还是拒绝?
- 如何按业务接口配置不同降级策略?
常见错误回答
“Redis 挂了就全部查数据库”或者“Redis 挂了就全部拒绝”。
评分点
- 初级:知道可以降级。
- 中级:能按场景区分。
- 高级:能设计分级限流、熔断、缓存保底和风险策略。
10.12 Go 客户端使用 Redis 的关键注意点
问题
Go 中使用 go-redis/v9 要注意什么?
推荐回答
面试现场版:
进程级共享 redis.Client,让连接池复用连接;每次调用传递 context.Context,设置建连、读写和连接池等待超时;区分 redis.Nil 与真正异常;写命令重试前判断幂等性;进程退出时统一关闭 Client。
展开:
还需要避免:
- 每请求创建 Client。
- 使用无 deadline 的 Context。
- 把缓存未命中当作 Redis 故障。
- 连接池无限等待。
- 超时后无条件重试非幂等写命令。
- 多 goroutine 共享同一个 Pipeline 或事务对象。
面试官追问
- Client 是否并发安全?
- Context 超时和 ReadTimeout 有何区别?
- 连接池耗尽时会发生什么?
常见错误回答
“每个 goroutine 应创建自己的 Redis Client。”
评分点
- 初级:会连接和执行 GET/SET。
- 中级:正确处理 Context、超时和
redis.Nil。 - 高级:能讨论连接池、重试幂等、观测和故障降级。
11. 一分钟面试回答
Redis 是以内存数据结构和命令语义为核心的数据服务器,它既可以作为缓存,也可以承担数据库、消息和协调组件的角色。判断它适不适合某个业务,不能只看速度,还要看数据模型、内存成本、持久化、复制一致性和故障恢复。Redis 通过高效数据结构、主要串行化的命令执行、事件驱动网络模型、长连接和 Pipeline 获得低延迟,但不能简单说整个 Redis 完全是单线程。单条 Redis 命令在同一节点上具有原子执行语义,但原子不等于已持久化、已复制或故障切换后不丢失,多条独立命令也不会自动组成事务。部署上,单机最简单;主从提供副本;Sentinel 解决非分片部署的监控、服务发现和自动切换;Cluster 同时提供分片与故障转移,但有跨 Slot 限制,并且仍不是强一致系统。Go 中应共享 go-redis/v9 Client,使用 Context 和超时,区分 redis.Nil 与真正异常,并谨慎处理写命令重试。
12. 本章总结
- Redis 不应被简单定义为缓存,它是可以承担多种架构角色的数据结构服务器。
- Redis 是否能作为权威数据库,取决于数据模型、内存、持久化、复制、备份和故障目标。
- 单机、主从、Sentinel、Cluster 解决的问题不同,复杂度也逐步增加。
- Redis 的基本访问模型是 Client 通过 RESP 向 Server 发送针对 Database、Key 和 Value 的命令。
- 单命令原子性不等于多命令事务、持久性、强一致或业务幂等。
- Redis 高性能来自内存、数据结构、命令路径、串行执行、长连接和批处理等共同因素。
go-redis.Client应长期共享,Context、超时、连接池和redis.Nil是工程使用的基础。
13. 自测清单
- Redis 作为缓存和作为权威数据库时,故障恢复目标有什么不同?
- 为什么 Redis 不能因为支持持久化就直接替代关系数据库?
- 本地缓存相比 Redis 少了一次网络访问,但增加了哪些一致性问题?
- 主从复制为什么不自动等于高可用?
- Sentinel 和 Cluster 分别解决什么问题?
- Redis Cluster 为什么只支持 Database 0?
- 单命令原子性为什么不能保证
GET加SET的组合安全? - RESP 中的 Null 与网络错误有什么区别?
SET后再执行EXPIRE存在哪个故障窗口?- 为什么一个
redis.Client可以被多个 goroutine 共享,而 Context 通常应该按请求创建?
14. 官方资料
- Redis Open Source 下载与当前版本。(Redis)
- Redis 8.8 版本说明。(Redis)
- Redis 官方 GitHub 仓库及产品定位。(GitHub)
- Redis 数据类型概览。(Redis)
- Redis Key、Value、TTL 与 Keyspace。(Redis)
- Redis Open Source Docker 安装。(Redis)
redis-cli官方文档。(Redis)- RESP 协议规范。(Redis)
- Redis 事务。(Redis)
- Redis Pipeline。(Redis)
- Redis 复制。(Redis)
- Redis Sentinel。(Redis)
- Redis Cluster 规范。(Redis)
- Redis
SELECT与逻辑数据库。(Redis) - Redis 性能与命令执行线程说明。(Redis)
go-redis/v9官方指南及仓库。(Redis)